package com.linkdood.app.web.rest;

import com.linkdood.app.constants.AuthoritiesConstants;
import com.linkdood.app.constants.ErrorCode;
import com.linkdood.app.domain.Account;
import com.linkdood.app.domain.User;
import com.linkdood.app.domain.UserData;
import com.linkdood.app.dto.DdioUserDTO;
import com.linkdood.app.dto.LoginDTO;
import com.linkdood.app.dto.LoginPhoneDTO;
import com.linkdood.app.dto.RegisterDTO;
import com.linkdood.app.exception.GeneralException;
import com.linkdood.app.exception.InvalidParamException;
import com.linkdood.app.exception.UserNotFoundException;
import com.linkdood.app.security.JwtFilter;
import com.linkdood.app.security.TokenProvider;
import com.linkdood.app.service.AccountService;
import com.linkdood.app.service.DdioService;
import com.linkdood.app.service.UserDataService;
import com.linkdood.app.service.UserService;
import com.linkdood.app.service.VerifyCodeService;
import com.linkdood.app.utils.MD5Util;
import com.linkdood.app.utils.ResourceUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

/**
 * 系统用户认证授权
 *
 * @author YunJ
 */
@Slf4j
@RestController
@RequestMapping("/api")
public class AuthenticationResource {

    private final TokenProvider tokenProvider;

    private final AuthenticationManagerBuilder authenticationManagerBuilder;

    private final DdioService ddioService;

    private final AccountService accountService;

    private final UserService userService;

    private final UserDataService userDataService;

    private final VerifyCodeService verifyCodeService;

    public AuthenticationResource(TokenProvider tokenProvider,
                                  AuthenticationManagerBuilder authenticationManagerBuilder,
                                  DdioService ddioService, AccountService accountService,
                                  UserService userService, UserDataService userDataService,
                                  VerifyCodeService verifyCodeService) {
        this.tokenProvider = tokenProvider;
        this.authenticationManagerBuilder = authenticationManagerBuilder;
        this.ddioService = ddioService;
        this.accountService = accountService;
        this.userService = userService;
        this.userDataService = userDataService;
        this.verifyCodeService = verifyCodeService;
    }

    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public ResponseEntity<JwtToken> authorize(@Valid @RequestBody LoginDTO loginDto) {
        String username = loginDto.getUsername();

        Optional<Account> account = accountService.getAccountByUserName(username);
        if (!account.isPresent()) {
            throw new UserNotFoundException();
        }
        if (StringUtils.isEmpty(account.get().getUserId())) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_NOT_BIND_USER);
        }

        String password = MD5Util.MD5Encode(ResourceUtil.base64Decode(loginDto.getPassword()));
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        boolean rememberMe = loginDto.getRememberMe() != null && loginDto.getRememberMe();
        String jwt = tokenProvider.createToken(authentication, rememberMe);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        return new ResponseEntity<>(new JwtToken(jwt), httpHeaders, HttpStatus.OK);
    }

    @ApiOperation(value = "用户登录 - 管理端")
    @PostMapping("/admin/login")
    public ResponseEntity<JwtToken> adminAuthorize(@Valid @RequestBody LoginDTO loginDto) {
        String username = loginDto.getUsername();

        Optional<Account> account = accountService.getAccountByUserName(username);
        if (!account.isPresent()) {
            throw new UserNotFoundException();
        }
        if (AuthoritiesConstants.USER.equals(account.get().getRole())) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_PERMISSION);
        }

        String password = MD5Util.MD5Encode(ResourceUtil.base64Decode(loginDto.getPassword()));
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);

        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        boolean rememberMe = loginDto.getRememberMe() != null && loginDto.getRememberMe();
        String jwt = tokenProvider.createToken(authentication, rememberMe);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        return new ResponseEntity<>(new JwtToken(jwt), httpHeaders, HttpStatus.OK);
    }

    /**
     * 默认普通用户
     */
    @ApiOperation(value = "第三方豆豆登录")
    @GetMapping("/thirdparty/login")
    public ResponseEntity<?> authorizeByCode(@RequestParam(required = false) String code,
                                             @RequestParam(required = false) String dood,
                                             @RequestParam(required = false, defaultValue = "false") boolean rememberMe, HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("成功进入第三方登录接口，code={}, dood={}", code, dood);
        if (StringUtils.isEmpty(code) && StringUtils.isEmpty(dood)) {
            throw new InvalidParamException();
        }

        UsernamePasswordAuthenticationToken authenticationToken;
        DdioUserDTO ddioUserDTO;
        try {
            if (StringUtils.isNotEmpty(code)) {
                // 验证 code
                ddioUserDTO = ddioService.doWithCode(code);
            } else {
                ddioUserDTO = new DdioUserDTO();
                ddioUserDTO.setUserId(dood);
            }

            userService.saveOrUpdateByDoodId(ddioUserDTO.getUserId(), ddioUserDTO.getUserToken());
        } catch (Exception e) {
            log.error("登录失败： 异常为：{}", e);
            throw new InvalidParamException("code");
        }
        authenticationToken = getAuthenticationToken(ddioUserDTO);
        log.info("auth : {}", authenticationToken);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        String jwt = tokenProvider.createToken(authenticationToken, rememberMe);
        String token = "Bearer " + jwt;
        log.info("登录成功， 返回token={}", token);
        response.setHeader("Authorization", token);
        return ResponseEntity.ok(ResourceUtil.success());
    }

    /**
     * 用户注册
     * @param req 入参
     * @return 注册成功
     */
    @ApiOperation(value = "用户(手机号)注册")
    @PostMapping("/register")
    public ResponseEntity<?> register(@Valid @RequestBody RegisterDTO req) {
        accountService.register(req);
        return ResponseEntity.ok(ResourceUtil.success());
    }

    /**
     * 用户名+验证码登录
     * @param req 登录参数
     * @return 认证token
     */
    @ApiOperation(value = "用户登录(手机号+验证码)")
    @PostMapping("/login/phone")
    public ResponseEntity<JwtToken> loginByPhone(@Valid @RequestBody LoginPhoneDTO req) {
        Optional<Account> accountOptional = accountService.getAccountByUserName(req.getPhone());
        if (!accountOptional.isPresent()) {
            throw new UserNotFoundException();
        }

        Account account = accountOptional.get();

        if (AuthoritiesConstants.USER.equals(account.getRole())) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_PERMISSION);
        }

        if (StringUtils.isEmpty(accountOptional.get().getUserId())) {
            throw new GeneralException(ErrorCode.ERROR_ACCOUNT_NOT_BIND_USER);
        }

        verifyCodeService.checkVerifyCode(req.getBid(), req.getPhone(), req.getCode());

        String[] roles = new String[1];
        roles[0] = account.getRole();
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(roles);

        // 设置用户名,角色信息,存入 Security 上下文
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(account.getId(), "", authorities);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

        boolean rememberMe = req.getRememberMe() != null && req.getRememberMe();
        String jwt = tokenProvider.createToken(usernamePasswordAuthenticationToken, rememberMe);

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);

        return new ResponseEntity<>(new JwtToken(jwt), httpHeaders, HttpStatus.OK);
    }

    /**
     * 第三方认证授权
     */
    private UsernamePasswordAuthenticationToken getAuthenticationToken(DdioUserDTO ddioUserDTO) {
        if (ddioUserDTO == null) {
            throw new UserNotFoundException();
        }
        log.debug("ddioUserDTO = {}", ddioUserDTO);

        String dood = ddioUserDTO.getUserId();
        if (StringUtils.isEmpty(dood)) {
            throw new UserNotFoundException("dood");
        }
        Optional<User> userOptional = userService.getUserByDoodId(dood);
        if (!userOptional.isPresent()) {
            throw new UserNotFoundException();
        }
        // 查询系统用户
        Account queryAccount = new Account();
        queryAccount.setUserId(userOptional.get().getId());
        Optional<Account> accountOptional = accountService.getAccount(userOptional.get().getId());
        Account account;
        if (!accountOptional.isPresent()) {
            // 创建用户信息
            account = new Account();
            account.setUserId(userOptional.get().getId());
            account.setRole(AuthoritiesConstants.USER);
            account.setUsername(accountService.checkUsername(dood));
            account = accountService.saveAccount(account, null);
            log.debug("user created successful, userID={}", dood);
        } else {
            // 更新 userToken
            account = accountOptional.get();
            Optional<UserData> userDataOptional = userDataService.getByUserId(userOptional.get().getId());
            if (userDataOptional.isPresent()) {
                UserData userData = userDataOptional.get();
                boolean canUpdateUserToken = ddioUserDTO.getUserToken() != null && !ddioUserDTO.getUserToken().equals(userData.getAccount());
                if (canUpdateUserToken) {
                    userData.setAccount(ddioUserDTO.getUserToken());
                    userDataService.saveOrUpdate(userData);
                }
            }
        }
        String[] roles = new String[1];
        roles[0] = account.getRole();
        List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(roles);
        return new UsernamePasswordAuthenticationToken(account.getId(), account.getPassword(), authorities);
    }

    /**
     * Object to return as body in JWT Authentication.
     */
    static class JwtToken {
        private String token;

        JwtToken(String token) {
            this.token = token;
        }

        public String getToken() {
            return token;
        }

        public void setToken(String token) {
            this.token = token;
        }
    }
}
